package service

import (
	"dbserver/database"
	"fmt"
	"os"
	"strings"
	l "util/log"
	"util/parser"
)

func syncTable(tbl_name string, tblinfo database.TblField) error {
	if oldfield, empty, _ := conn.QueryTblInfo(db, tbl_name); empty {
		err := conn.CreateTable(tbl_name, tblinfo)
		if err != nil {
			panic(err)
		}
		l.TraceInfo("sync", "create table ", tbl_name, " ok")
	} else {
		alert := make([]database.Field, 0, 10)
		add := make([]database.Field, 0, 10)
		for _, newf := range tblinfo.FieldList {
			find := false
			for _, oldf := range oldfield.FieldList {
				if strings.ToLower(newf.Name) == strings.ToLower(oldf.Name) {
					if strings.ToLower(oldf.Type) != strings.ToLower(newf.Type) {
						alert = append(alert, newf)
					}
					find = true
					break
				}
			}
			if !find {
				add = append(add, newf)
			}
		}

		for _, f := range alert {
			var nulltype string
			if !f.IsNull {
				nulltype = "NOT NULL"
			} else {
				nulltype = "DEFAULT NULL"
			}
			autoinc := ""
			if f.AutoInc {
				autoinc = " AUTO_INCREMENT"
			}

			sql := fmt.Sprintf("ALTER TABLE `%s` CHANGE `%s` `%s` %s %s %s;", tbl_name, f.Name, f.Name, f.Type, nulltype, autoinc)
			_, err := conn.Exec(sql)
			if err != nil {
				panic(err)
			}

			l.TraceInfo("sync", sql, " ok")
		}

		for _, f := range add {
			var nulltype string
			if !f.IsNull {
				nulltype = "NOT NULL"
			} else {
				nulltype = "DEFAULT NULL"
			}
			autoinc := ""
			if f.AutoInc {
				autoinc = " AUTO_INCREMENT"
			}

			sql := fmt.Sprintf("ALTER TABLE `%s` ADD `%s` %s %s %s;", tbl_name, f.Name, f.Type, nulltype, autoinc)
			_, err := conn.Exec(sql)
			if err != nil {
				panic(err)
			}

			l.TraceInfo("sync", sql, "ok")
		}
	}

	return nil
}

func syncObject(obj parser.Object) {
	tbl_name := "tbl_" + strings.ToLower(obj.Name)
	tblinfo := obj.CreateTable()
	syncTable(tbl_name, tblinfo)

	//子表
	tblinfo = database.TblField{}
	tblinfo.FieldList = []database.Field{
		database.Field{"id", "BIGINT(20) UNSIGNED", false, false},
		database.Field{"child_id", "BIGINT(20) UNSIGNED", false, false},
		database.Field{"type", "VARCHAR(50)", false, false},
		database.Field{"index", "INT(10)", false, false},
		database.Field{"delete", "TINYINT(1) UNSIGNED", false, false}}
	tblinfo.IndexInfo = map[string][]string{
		"INDEX `id`":              []string{"id"},
		"INDEX `parent_child_id`": []string{"id", "child_id"}}

	tbl_name = strings.ToLower(fmt.Sprintf("tbl_%s_child", strings.ToLower(obj.Name)))
	syncTable(tbl_name, tblinfo)

	records := obj.CreateRecordTable()
	for k, _ := range records {
		tbl_name = strings.ToLower(fmt.Sprintf("tbl_%s_%s", obj.Name, k))

		syncTable(tbl_name, records[k])
	}
}

func syncSysDb() {
	//取用户名
	row, _ := conn.Query("SELECT CURRENT_USER()")
	row.Next()
	var user string
	row.Scan(&user)
	row.Close()

	//ID表
	tblinfo := database.TblField{}
	tblinfo.FieldList = []database.Field{
		database.Field{"id", "BIGINT(20) UNSIGNED", false, true},
		database.Field{"stub", "VARCHAR(10)", false, false}}
	tblinfo.IndexInfo = map[string][]string{
		"PRIMARY":             []string{"id"},
		"UNIQUE INDEX `stub`": []string{"stub"}}
	syncTable("sys_uid", tblinfo)

	conn.Exec("DROP FUNCTION IF EXISTS `createuid`")
	conn.Exec(fmt.Sprintf(
		`CREATE DEFINER=%s FUNCTION createuid() RETURNS bigint(20)
BEGIN
REPLACE INTO sys_uid (stub) VALUES ('a'); 
SELECT LAST_INSERT_ID() into @s; 
RETURN @s;
END
`, user))
}

func syncDB(path string) {

	syncSysDb()

	dir, _ := os.Open(path)
	files, _ := dir.Readdir(0)

	for _, f := range files {
		if !f.IsDir() {
			obj := parser.ParseEntity(path + "/" + f.Name())
			syncObject(obj)
			l.TraceInfo("sync", "process ", obj.Name, " complete")
		}
	}

	l.TraceInfo("sync", "all complete")
}

func checkTable(tbl_name string, tblinfo database.TblField) bool {
	if oldfield, empty, _ := conn.QueryTblInfo(db, tbl_name); empty {
		return false
	} else {
		for _, newf := range tblinfo.FieldList {
			find := false
			for _, oldf := range oldfield.FieldList {
				if strings.ToLower(newf.Name) == strings.ToLower(oldf.Name) {
					if strings.ToLower(oldf.Type) != strings.ToLower(newf.Type) {
						return false
					}
					find = true
					break
				}
			}
			if !find {
				return false
			}
		}
	}
	return true
}

func checkObject(obj parser.Object) bool {
	tbl_name := "tbl_" + strings.ToLower(obj.Name)
	tblinfo := obj.CreateTable()
	if !checkTable(tbl_name, tblinfo) {
		return false
	}

	//子表
	tblinfo = database.TblField{}
	tblinfo.FieldList = []database.Field{
		database.Field{"id", "BIGINT(20) UNSIGNED", false, false},
		database.Field{"child_id", "BIGINT(20) UNSIGNED", false, false},
		database.Field{"type", "VARCHAR(50)", false, false},
		database.Field{"index", "INT(10)", false, false},
		database.Field{"delete", "TINYINT(1) UNSIGNED", false, false}}
	tblinfo.IndexInfo = map[string][]string{
		"INDEX `id`":              []string{"id"},
		"INDEX `parent_child_id`": []string{"id", "child_id"}}

	tbl_name = strings.ToLower(fmt.Sprintf("tbl_%s_child", strings.ToLower(obj.Name)))
	if !checkTable(tbl_name, tblinfo) {
		return false
	}

	records := obj.CreateRecordTable()
	for k, _ := range records {
		tbl_name = strings.ToLower(fmt.Sprintf("tbl_%s_%s", obj.Name, k))

		if !checkTable(tbl_name, records[k]) {
			return false
		}
	}

	return true
}

func checkSysDb() bool {
	//检查系统表
	tblinfo := database.TblField{}
	tblinfo.FieldList = []database.Field{
		database.Field{"id", "BIGINT(20) UNSIGNED", false, true},
		database.Field{"stub", "VARCHAR(10)", false, false}}
	tblinfo.IndexInfo = map[string][]string{
		"PRIMARY":             []string{"id"},
		"UNIQUE INDEX `stub`": []string{"stub"}}

	if !checkTable("sys_uid", tblinfo) {
		return false
	}

	return true
}

func checkDb(path string) bool {

	if !checkSysDb() {
		l.TraceInfo("dbmgr", ` system table need sync, you need use "-sync" to sync the database`)
		return false
	}

	dir, _ := os.Open(path)
	files, _ := dir.Readdir(0)

	for _, f := range files {
		if !f.IsDir() {
			obj := parser.ParseEntity(path + "/" + f.Name())
			if !checkObject(obj) {
				l.TraceInfo("dbmgr", f.Name(), ` is changed, you need use "-sync" to sync the database `)
				return false
			}

		}
	}

	l.TraceInfo("dbmgr", "check db complete")

	return true
}
